/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package java.io;

import com.jtransc.JTranscSystem;
import com.jtransc.JTranscSystemProperties;

import java.util.Formatter;

public final class Console implements Flushable {
	private static final Object CONSOLE_LOCK = new Object();

	private static final Console console = makeConsole();

	private final ConsoleReader reader;
	private final PrintWriter writer;

	/**
	 * Secret accessor for {@code System.console}.
	 *
	 * @hide
	 */
	public static Console getConsole() {
		return console;
	}

	private static Console makeConsole() {
		try {
			return new Console(System.in, System.out);
		} catch (IOException ex) {
			throw new AssertionError(ex);
		}
	}

	private Console(InputStream in, OutputStream out) throws IOException {
		this.reader = new ConsoleReader(in);
		this.writer = new ConsoleWriter(out);
	}

	public void flush() {
		writer.flush();
	}

	/**
	 * Writes a formatted string to the console using
	 * the specified format string and arguments.
	 *
	 * @param format the format string (see {@link Formatter#format})
	 * @param args   the list of arguments passed to the formatter. If there are
	 *               more arguments than required by {@code format},
	 *               additional arguments are ignored.
	 * @return the console instance.
	 */
	public Console format(String format, Object... args) {
		Formatter f = new Formatter(writer);
		f.format(format, args);
		f.flush();
		return this;
	}

	/**
	 * Equivalent to {@code format(format, args)}.
	 */
	public Console printf(String format, Object... args) {
		return format(format, args);
	}

	/**
	 * Returns the {@link Reader} associated with this console.
	 */
	public Reader reader() {
		return reader;
	}

	/**
	 * Reads a line from the console.
	 *
	 * @return the line, or null at EOF.
	 */
	public String readLine() {
		try {
			return reader.readLine();
		} catch (IOException e) {
			throw new IOError(e);
		}
	}

	/**
	 * Reads a line from this console, using the specified prompt.
	 * The prompt is given as a format string and optional arguments.
	 * Note that this can be a source of errors: if it is possible that your
	 * prompt contains {@code %} characters, you must use the format string {@code "%s"}
	 * and pass the actual prompt as a parameter.
	 *
	 * @param format the format string (see {@link Formatter#format})
	 * @param args   the list of arguments passed to the formatter. If there are
	 *               more arguments than required by {@code format},
	 *               additional arguments are ignored.
	 * @return the line, or null at EOF.
	 */
	public String readLine(String format, Object... args) {
		synchronized (CONSOLE_LOCK) {
			format(format, args);
			return readLine();
		}
	}

	/**
	 * Reads a password from the console. The password will not be echoed to the display.
	 *
	 * @return a character array containing the password, or null at EOF.
	 */
	public char[] readPassword() {
		synchronized (CONSOLE_LOCK) {
			int previousState = setEcho(false, 0);
			try {
				String password = readLine();
				writer.println(); // We won't have echoed the user's newline.
				return (password == null) ? null : password.toCharArray();
			} finally {
				setEcho(true, previousState);
			}
		}
	}

	private static int setEcho(boolean on, int previousState) {
		try {
			return setEchoImpl(on, previousState);
		} catch (IOException ex) {
			throw new IOError(ex);
		}
	}

	private static native int setEchoImpl(boolean on, int previousState) throws IOException;

	/**
	 * Reads a password from the console. The password will not be echoed to the display.
	 * A formatted prompt is also displayed.
	 *
	 * @param format the format string (see {@link Formatter#format})
	 * @param args   the list of arguments passed to the formatter. If there are
	 *               more arguments than required by {@code format},
	 *               additional arguments are ignored.
	 * @return a character array containing the password, or null at EOF.
	 */
	public char[] readPassword(String format, Object... args) {
		synchronized (CONSOLE_LOCK) {
			format(format, args);
			return readPassword();
		}
	}

	/**
	 * Returns the {@link Writer} associated with this console.
	 */
	public PrintWriter writer() {
		return writer;
	}

	private static class ConsoleReader extends BufferedReader {
		public ConsoleReader(InputStream in) throws IOException {
			super(new InputStreamReader(in, JTranscSystemProperties.fileEncoding()), 256);
			lock = CONSOLE_LOCK;
		}

		@Override
		public void close() {
			// Console.reader cannot be closed.
		}
	}

	private static class ConsoleWriter extends PrintWriter {
		public ConsoleWriter(OutputStream out) {
			super(out, true);
			lock = CONSOLE_LOCK;
		}

		@Override
		public void close() {
			// Console.writer cannot be closed.
			flush();
		}
	}
}
